package pl.tssoft.math.core.parser;

import java.util.NoSuchElementException;
import pl.tssoft.math.core.Value;
import pl.tssoft.math.core.ValueFactory;
import pl.tssoft.math.core.calculation.operator.Operator;
import pl.tssoft.math.core.calculation.operator.OperatorFactory;
import pl.tssoft.math.core.calculation.operator.builtin.special.EndingOperator;
import pl.tssoft.math.core.exceptions.ErrorCode;
import pl.tssoft.math.core.exceptions.InternalException;
import pl.tssoft.math.core.exceptions.SyntaxException;

/**
 *
 * @author tserafin
 */
public class ParserUtil {

    public static ElementType getCurrentElementType(InputIterator input) {
        if (!input.isPresent()) {
            return ElementType.OPERATOR_OR_END_OF_EXPRESSION;    //it will be parsed as ending operator
        }

        Character current = input.get();
        Character next = input.getNext();
        if (Character.isLetter(current)) {
            return ElementType.NAME;
        }
        if (Character.isDigit(current) || current.equals('.') && Character.isDigit(next)) {
            return ElementType.NUMBER;
        }
        if (current.equals('(')) {
            return ElementType.BRACKET_BLOCK;
        }
        if (current.equals('[')) {
            return ElementType.SQUARE_BLOCK;
        }
        return ElementType.OPERATOR_OR_END_OF_EXPRESSION;
    }

    //input has to point a letter
    static String readName(InputIterator input) {
        Character c = input.get();
        if (c == null || !Character.isAlphabetic(c)) {
            throw new InternalException("internal error, input does not point an alphabetic character", null);
        }
        StringBuilder sb = new StringBuilder();
        while (input.isPresent()) {
            c = input.get();
            if (!Character.isLetterOrDigit(c)) {
                break;
            }
            sb.append(c);
            input.forward();
        }
        return sb.toString();
    }

    private enum NumberPart {

        START, AFTER_DOT, AFTER_E;
    }

    /**
     * Parses number in format: [ddd][.]ddd[e|E[+|-]ddd][i|j]
     */
    public static Value parseNumber(InputIterator input) throws SyntaxException {
        NumberPart step = NumberPart.START;
        boolean imag = false;
        StringBuilder sb = new StringBuilder();
        Character c;
        for (; input.isPresent(); input.forward(), sb.append(c)) {
            c = input.get();
            if (Character.isDigit(c)) {
                continue;
            }
            if (c == '.') {
                if (step != NumberPart.START) {
                    break; //can be 1.3./2 - it's ok
                }
                step = NumberPart.AFTER_DOT;
                continue;
            }
            if (c == 'e' || c == 'E') {
                if (step == NumberPart.AFTER_E) {
                    break;
                }
                sb.append(c);
                c = input.forwardAndGet();
                if (c != null && (c == '+' || c == '-')) {
                    sb.append(c);
                    c = input.forwardAndGet();
                }
                if (c == null || !Character.isDigit(c)) {
                    throw new SyntaxException(input.getPosition(), ErrorCode.ERR_INVALID_NUMBER_FORMAT);
                }
                step = NumberPart.AFTER_E;
                continue;
            }
            if (c == 'i' || c == 'j') {
                input.forward();
                imag = true;
                //end of parsing
                break;
            }
            break;
        }
        try {
            double d = Double.parseDouble(sb.toString());
            return ValueFactory.createScalarValue(imag ? 0 : d, imag ? d : 0);
        } catch (NumberFormatException e) {
            throw new InternalException("Problem with parsing string: " + sb.toString(), e);
        }
    }

    //todo testy
    public static Operator parseOperator(InputIterator input, ParserConfig config) throws SyntaxException {
        if (!input.isPresent()) {
            return new EndingOperator();
        }

        StringBuilder sb = new StringBuilder();
        int startPosition = input.getPosition();
        Character c = input.getAndForward();
        sb.append(c);

        if (config.expressionAllEndingCharacters.contains(sb.toString())) {
            return new EndingOperator(sb.toString());
        }

        if (c == ',' || c == ';') {
            throw new SyntaxException(startPosition, ErrorCode.ERR_UNEXPECTED_OPERATOR);
        }

        //todo dot operators, e.g. .*

        try {
            return OperatorFactory.create(sb.toString());
        } catch (NoSuchElementException e) {
            throw new SyntaxException(startPosition, ErrorCode.ERR_UNKNOWN_OPERATOR);
        }
    }

    static void assertInputPointsCharacterAndForward(InputIterator input, char c) {
        if (!input.isPresent() || !input.get().equals(c)) {
            throw new InternalException("expected character " + c);
        }
        input.forward();
    }
}
